const {BusiError, SysError} = require('../exception/exception');
const rsa = require('../encode/Rsa');
const config = require('../config/config');
const axi = require('axios');
const uuidUtil = require('../util/uuidUtil');
const xmlUtil = require('../util/xmlUtil');
const dateUtil = require('../util/dateUtil');
const stringUtil = require('../util/stringUtil');
const idUtil = require('../util/idUtil');
const payOrderDao = require('../dao/payOrderDao');
const mysqlPool = require('../dao/MySQLPool');
const userDao = require('../dao/userDao');

const busiConst = require('../const/busiConst');
const payConst = require('../const/payConst');

const payOrderCategoryService = require('./PayOrderCategoryService');

const axiosConfig = {
    timeout: 15000,
    baseURL: config.conf["pay.qft.baseUrl"],
};

const axios = axi.create(axiosConfig);

class QftWechatQrCodePayService {
    constructor() {
        this.payChannel = "J002";
        this.notifyUrl = config.conf["pay.qft.notifyUrl"] + "/wechat";
        this.service = "pay.weixin.native";
        this.mchId = config.conf["pay.qft.mchId"];
    }

    /**
     *
     * @param {String} uid
     * @param {int} amount
     * @returns {Promise<void>}
     */
    async pay(uid, amount, category) {
        let payEntity = {};
        payEntity.service = this.service;
        payEntity.version = "2.0";
        // payEntity.charset = "UTF-8";
        payEntity.sign_type = "RSA_1_256";
        payEntity.mch_id = this.mchId;
        let outTradeNo = busiConst.TYPE_RECHARGE_OUT_TRADE_NO + config.machineNo + dateUtil.dateFormat("yyyyMMddhhmmss", new Date())
            + stringUtil.padZeroSix(idUtil.nextSix());
        payEntity.out_trade_no = outTradeNo;
        // payEntity.device_info = "00";
        // payEntity.op_user_id = "jk";
        // payEntity.op_shop_id = "id";
        payEntity.body = "JKWechatQrCodeOrder";
        // payEntity.attach = "just_attach";
        payEntity.total_fee = amount;
        // payEntity.limit_credit_pay = 0;
        payEntity.mch_create_ip = "127.0.0.1";
        payEntity.notify_url = this.notifyUrl;
        // payEntity.device_info = "iOS_WAP";
        // payEntity.mch_app_name = "JK";
        // payEntity.mch_app_id = "https://m.jd.com";
        // payEntity.time_start = "20190331091010";
        // payEntity.time_expire = "20190431091010";
        let nonceStr = uuidUtil.uuid();
        payEntity.nonce_str = nonceStr;
        payEntity.callback_url = config.conf["pay.qft.callBackUrl"];
        // payEntity.time_start = "";
        // payEntity.time_expire = "";

        let expireTime = new Date();//你已知的时间
        let timestamp = expireTime.getTime();//转化为时间戳毫秒数
        expireTime.setTime(timestamp + 1000 * 60 * 10);//设置新时间比旧时间多一小时
        let expireTimeStr = dateUtil.dateFormat("yyyyMMddhhmmss", expireTime);
        payEntity.time_expire = expireTimeStr;

        let order = {
            uid: uid,
            payChannel: this.payChannel,
            amount: amount,
            outTradeNo: outTradeNo,
            payOrderType: payConst.ORDER_TYPE_RECHARGE,
            tradeType: this.service,
            expireTime: expireTime,
            category: category
        };

        let saveOrderId = await payOrderDao.save(order);
        if (saveOrderId <= 0) {
            throw new BusiError(500, "创建订单失败");
        }
        order.id = saveOrderId;

        let signStr = stringUtil.getSignStr(payEntity);
        payEntity.sign = await rsa.sign256(signStr);

        let jsonXml = xmlUtil.entity2xml(payEntity);
        console.logw("创建订单请求报文:", jsonXml);

        let json2 = await xmlUtil.xml2json(jsonXml);
        let resp = await axios.post("", jsonXml);
        let json = await xmlUtil.xml2json(resp.data);
        console.logw("创建订单返回报文:", JSON.stringify(json));

        if (json.xml.status !== '0') {
            order.errCode = json.xml.status;
            order.errMsg = json.xml.message;
            order.status = payConst.payOrderStatus.orderClose;
            let updateRes = await payOrderDao.updateByOutTradeNo(order);
            if (updateRes <= 0){
                console.log("更新订单异常信息出错:", order);
                throw new BusiError(500, "更新订单异常信息出错");
            }
            throw new BusiError(500, "交易通信异常:" + order.errMsg);
        }

        if (json.xml.result_code !== '0') {
            console.logw("创建订单失败:", JSON.stringify(json));
            order.errCode = json.xml.err_code;
            order.errMsg = json.xml.err_msg;
            order.status = payConst.payOrderStatus.orderClose;
            let updateRes = await payOrderDao.updateByOutTradeNo(order);
            if (updateRes <= 0){
                console.log("更新订单异常信息出错:", order);
                throw new BusiError(500, "更新订单异常信息出错:" + order.errMsg);
            }
            throw new BusiError(500, "交易失败:" + order.errMsg);
        }

        //
        let resSignStr = stringUtil.getSignStr(json.xml);
        let isValid = await rsa.verify256(resSignStr, json.xml.sign);
        if (!isValid) {
            console.logw("订单返回报文验签失败:", JSON.stringify(json));
            order.remark = "订单返回报文验签失败";
            let updateRes = await payOrderDao.updateByOutTradeNo(order);
            if (updateRes <= 0){
                console.log("更新订单异常信息出错:", order);
                throw new BusiError(500, "更新订单异常信息出错");
            }
            throw new BusiError(500, "验签失败");
        }

        return {codeUrl: json.xml.code_url, codeImgUrl: json.xml.code_img_url};
    }

    async payNotify(notify) {
        if (!notify.xml) {
            console.log("receive pay notify err:", notify);
            throw new BusiError(500, "回调通知状态异常");
        }

        notify = notify.xml;
        if (notify.status !== '0') {
            console.log("receive pay notify err[1]:", notify);
            throw new BusiError(500, "回调通知状态异常");
        }
        
        if (notify.result_code !== '0'){
            console.log("receive pay notify err[2]:", notify);
            throw new BusiError(500, "回调通知状态异常");
        }

        // out_trade_no
        let order = await payOrderDao.getByOutTradeNo(notify.out_trade_no);
        if (!order){
            console.log("receive pay notify err[3], 查无此订单:", notify);
            throw new BusiError(500, "查无此订单");
        }
        order.outTradeNo = notify.out_trade_no;

        if (order.status === payConst.payOrderStatus.orderSuccess){
            console.log("此订单已经交易成功:", order);
            return;
        }

        let resSignStr = stringUtil.getSignStr(notify);
        let isValid = await rsa.verify256(resSignStr, notify.sign);
        if (!isValid) {
            console.logw("回调通知返回报文验签失败:", JSON.stringify(notify));
            order.errCode = notify.err_code;
            order.errMsg = notify.err_msg;
            order.remark = "订单返回报文验签失败";
            let updateRes = await payOrderDao.updateByOutTradeNo(order);
            if (updateRes <= 0){
                console.log("更新订单异常信息出错:", order);
                throw new BusiError(500, "更新订单异常信息出错");
            }
            throw new BusiError(500, "验签失败");
        }

        let payResult = notify.pay_result;
        let timeEnd = notify.time_end;
        if(!timeEnd){
            order.finishTime = new Date();
        }else{
            order.finishTime = dateUtil.yyyyMMddHHmmssToDate(timeEnd);
        }
        order.status = payConst.payOrderStatus.orderFailed;
        if (payResult === "0") {
            order.status = payConst.payOrderStatus.orderSuccess;
            order.tradeNo = notify.transaction_id;
            order.finishTime = dateUtil.yyyyMMddHHmmssToDate(notify.time_end);
            order.bankType = notify.bank_type;
        }

        await payOrderCategoryService.orderProcess(order.category, order);
    }

    async payQuery(outTradeNo) {
        let order = await payOrderDao.getByOutTradeNo(outTradeNo);
        if (!order){
            console.log("receive pay notify err[3], 查无此订单:", outTradeNo);
            throw new BusiError(500, "查询失败");
        }
        order.outTradeNo = outTradeNo;

        if (order.status === payConst.payOrderStatus.orderSuccess){
            console.log("此订单已经交易成功:", order);
            throw new BusiError(500, "此订单已经交易成功");
        }

        let queryEntity = {};
        queryEntity.service = "unified.trade.query";
        queryEntity.version = "2.0";
        // payEntity.charset = "UTF-8";
        queryEntity.sign_type = "RSA_1_256";
        queryEntity.mch_id = this.mchId;
        queryEntity.out_trade_no = outTradeNo;
        queryEntity.nonce_str = uuidUtil.uuid();

        let signStr = stringUtil.getSignStr(queryEntity);

        let sign = await rsa.sign256(signStr);
        queryEntity.sign = sign;

        let jsonXml = xmlUtil.entity2xml(queryEntity);
        console.logw("查询订单请求报文:", jsonXml);

        let json2 = await xmlUtil.xml2json(jsonXml);
        let resp = await axios.post("", jsonXml);
        let json = await xmlUtil.xml2json(resp.data);
        console.logw("查询订单返回报文:", JSON.stringify(json));

        if (json.xml.status !== '0') {
            order.errMsg = json.xml.message;
            let updateRes = await payOrderDao.updateByOutTradeNo(order);
            if (updateRes <= 0){
                console.log("更新订单异常信息出错:", order);
                throw new BusiError(500, "更新订单异常信息出错:" + json.xml.message);
            }
            throw new BusiError(500, "交易通信异常:" + json.xml.message);
        }

        let msg = "";
        if (json.xml.result_code !== '0') {
            console.logw("创建订单失败:", JSON.stringify(json));
            // 对超出截止日期5分钟的订单做处理
            let expireTime = new Date(order.expire_time);
            if (expireTime.getTime() + 1000 * 60 * 5 < new Date().getTime()) {
                order.remark = "订单超时关闭";
                order.status = payConst.payOrderStatus.orderClose;
            }

            order.errCode = json.xml.err_code;
            order.errMsg = json.xml.err_msg;
            msg = json.xml.err_code + ":" + json.xml.err_msg;
            let updateRes = await payOrderDao.updateByOutTradeNo(order);
            if (updateRes <= 0){
                console.log("更新订单异常信息出错:", order);
                throw new BusiError(500, "更新订单异常信息出错:" + json.xml.message);
            }
            throw new BusiError(500, "交易失败:" + json.xml.message);
        }

        //
        let resSignStr = stringUtil.getSignStr(json.xml);
        let isValid = await rsa.verify256(resSignStr, json.xml.sign);
        if (!isValid) {
            console.logw("订单返回报文验签失败:", JSON.stringify(json));
            order.remark = "订单返回报文验签失败";
            let updateRes = await payOrderDao.updateByOutTradeNo(order);
            if (updateRes <= 0){
                console.log("更新订单异常信息出错:", order);
                throw new BusiError(500, "更新订单异常信息出错");
            }
            throw new BusiError(500, "验签失败");
        }

        // SUCCESS—支付成功
        // REFUND—转入退款
        // NOTPAY—未支付
        // CLOSED—已关闭
        // PAYERROR—支付失败(其他原因，如银行返回失败)
        order.status = payConst.payOrderStatus.orderFailed;
        order.tradeState = json.xml.trade_state;
        if (json.xml.trade_state === 'NOTPAY'){
            order.status = payConst.payOrderStatus.orderInit;
            let expireTime = new Date(order.expire_time);
            if (expireTime.getTime() + 1000 * 60 * 5 < new Date().getTime()) {
                order.remark = "订单超时关闭";
                order.status = payConst.payOrderStatus.orderClose;
            }
        }

        if (json.xml.trade_state === 'SUCCESS') {
            order.status = payConst.payOrderStatus.orderSuccess;
            order.tradeNo = json.xml.transaction_id;
            if(!json.xml.time_end){
                order.finishTime = new Date();
            }else{
                order.finishTime = dateUtil.yyyyMMddHHmmssToDate(json.xml.time_end);
            }
            order.bankType = json.xml.bank_type;
        }

        await mysqlPool.doWithTx(async function (conn) {
            let res2 = await payOrderDao.updateByOutTradeNoWithTx(conn, order);
            if (res2 < 1){
                console.logw("更新订单失败:", order);
                throw new BusiError(500, "更新订单失败");
            }

            if (order.status === payConst.payOrderStatus.orderSuccess){
                let res1 = await userDao.incCoinWithTx(conn, order.uid, parseFloat(order.amount/100.00).toFixed(2));
                if (res1 < 1) {
                    console.logw("inc user coin err: res1 <= 0");
                    throw new BusiError(500, "增加余额失败");
                }
            }
        });

        return msg;
    }

    async refund(outTradeNo, refundAmt) {
        let refundEntity = {};
        refundEntity.service = "unified.trade.refund";
        // refundEntity.version = "1.0";
        // refundEntity.charset = "UTF-8";
        // refundEntity.sign_type = "MD5";
        refundEntity.mch_id = this.mchId;
        refundEntity.out_trade_no = outTradeNo;

        // todo: create refund no
        let outRefundNo = busiConst.TYPE_RECHARGE_OUT_REFUND_NO + config.machineNo + dateUtil.dateFormat("yyyyMMddhhmmss")
            + stringUtil.padZeroSix(idUtil.nextSix());
        refundEntity.out_refund_no = outRefundNo;

        // query db
        let res = {};
        refundEntity.total_fee = res.amount;
        refundEntity.refund_fee = refundAmt;
        refundEntity.op_user_id = this.mchId;
        refundEntity.refund_channel = "ORIGINAL";
        refundEntity.nonce_str = uuidUtil.uuid();


        let signStr = stringUtil.getSignStr(refundEntity);
        let sign = await rsa.sign256(signStr);
        refundEntity.sign = sign;

        let jsonXml = xmlUtil.entity2xml(refundEntity);
        console.log("final str:", jsonXml);

        let resp = await axios.post("", jsonXml);
        console.log("data:", resp.data);


        let json = await xmlUtil.xml2json(resp.data);
        console.log(JSON.stringify(json));
    }

    async refundQuery(outRefundNo) {
        let refundQueryEntity = {};
        refundQueryEntity.service = "unified.trade.refundquery";
        // refundQueryEntity.version = "1.0";
        // refundQueryEntity.charset = "UTF-8";
        // refundQueryEntity.sign_type = "MD5";
        refundQueryEntity.mch_id = this.mchId;
        refundQueryEntity.out_refund_no = outRefundNo;
        refundQueryEntity.nonce_str = uuidUtil.uuid();

        let signStr = stringUtil.getSignStr(refundQueryEntity);
        let sign = await rsa.sign256(signStr);
        refundQueryEntity.sign = sign;

        let jsonXml = xmlUtil.entity2xml(refundQueryEntity);
        console.log("final str:", jsonXml);

        let resp = await axios.post("", jsonXml);
        console.log("data:", resp.data);


        let json = await xmlUtil.xml2json(resp.data);
        console.log(JSON.stringify(json));
    }
}


let qftWechatQrCodePayService = new QftWechatQrCodePayService();
module.exports = qftWechatQrCodePayService;